/*
 *	Atividade 1 - Multiplicacao de matrizes em MPI
 *	Programacao Concorrente - SCC0143 - 2014
 *
 *	Henrique Teixeira Arroyo	6419241
 *	Henry D. Leão				5881632
 *	Júlio Zanzini Polzato		7288971
 * 
 *
 * Foi utilizada a mesma abordagem de paralelização do exemplo fornecido pelo professor.
 * A diferença está na utilização de funções diferentes para a comunicação entre os processos.
 * Para auxiliar no cálculo de cada processador, foi utilizada a biblioteca pthread.
 * 
 * 
 */

#include <mpi.h>
#include <stdio.h>
#include <stdlib.h> 
#include <pthread.h>

<<<<<<< .mine
#define NUM_THREADS 1	6	// Numero de threads a serem executadas em cada nó do cluster
=======
#define NUM_THREADS 16			// Numero de threads a serem executadas em cada nó do cluster
>>>>>>> .r158

int **A, **B, **C;				// Matrizes a serem utilizadas

int s[3],						// Valores de entrada n, m e p, respectivamente 
	myrank, 					// Valor do processo
	P, 							// Numero de processadores
	from, 						// Linha de A a começar o processamento
	to;							// Linha de A a terminar de processar

//Funcao auxiliar imprime a matriz 'M' de 'l' linhas e 'c' colunas
void print(int **M, int l, int c){
	int i,j;

	for(i=0;i<l;i++){
		for(j=0;j<c;j++)
			printf("%d ",M[i][j]);
		printf("\n");
	}
}

/* Funcao a ser executada por cada thread
 *
 * Cada thread irá calcular um bloco da matriz C de dimensão '(to-from)' linhas e 'Bcols' colunas
 */
void *threadedMM(void *threadID){
	
	int i,j,k,taskid, *id_ptr, Boffset, Bcols;

    id_ptr = (int *) threadID;
    taskid = *id_ptr;
	
	Bcols = s[2]/NUM_THREADS;		// Numero de colunas de B a ser processada por cada thread
	Boffset = Bcols*taskid;			// Offset de colunas de B a começar o processamento
	
	if(taskid==NUM_THREADS-1)		
		Bcols+=s[2]%NUM_THREADS;	// A ultima thread executa as colunas extras que sobram da divisao

	//printf("Task %d running A rows from %d to %d - Thread %d running B columns from %d to %d\n",myrank,from,to,taskid,Boffset,Boffset+Bcols);
	
	// Calculo da multiplicacao
	for (i=from; i<to; i++){ 
		for (j=Boffset; j<(Boffset+Bcols); j++) {
			C[i][j]=0;
			for (k=0; k<s[1]; k++)
				C[i][j] += A[i][k]*B[k][j];
		}
	}
<<<<<<< .mine
=======
	
	//getLocalMax(C, Boffset, Boffset+Bcols);
>>>>>>> .r158
}

/* Funcao chamada por cada no
 * irá executar as threads para o calculo da multiplicacao
 * e aguardar o seu termino
 */
void runThreads(){

	pthread_t run_threads;
		
	int i, error, *taskids[NUM_THREADS];
    pthread_t threads[NUM_THREADS];
   
    for(i=0;i<NUM_THREADS;i++){
		taskids[i] = (int *) malloc(sizeof(int));
		*taskids[i] = i;

    	error = pthread_create(&threads[i],NULL,threadedMM,(void *)taskids[i]);
		
		if(error){
			printf("error at task %d creating thread %d \n",myrank,i);
			pthread_exit(NULL);
		}
    }	

	for(i=0;i<NUM_THREADS;i++){		    // sum up the circle hits
		pthread_join(threads[i],NULL);
	}
}

/*
 * Funcao aloca uma matriz de l linhas e c colunas de maneira contigua na memória.
 * (valores armazenados sequencialmente).
 */
int** allocMatrix(int l, int c){
	int i;
	int *m = (int*)malloc(sizeof(int)*l*c);
	int **M = (int**)malloc(sizeof(int*)*l);
	
	for(i=0;i<l;i++)
		M[i] = &m[c*i];
	
	return M;		
}

int main(int argc, char *argv[]){

	int i, j, k, v;
	int *displs, 		// Offset de envio de dados para cada processador
		*sendcount,		// Contagem de dados a serem enviados para cada processador
		task_rows, 		// Numero de linhas de A a serem enviadas a cada processador
		extra_rows;		// Linhas extras de A enviadas para o ultimo processador
		
	MPI_Status status;
	
	MPI_Init (&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &myrank);	
	MPI_Comm_size(MPI_COMM_WORLD, &P); 
	
	// Le dimensoes das matrizes de entrada
	scanf("%d%d%d",&s[0],&s[1],&s[2]);
	
	// Envia dimensoes para nos escravos
	MPI_Bcast(s, 3, MPI_INT, 0, MPI_COMM_WORLD);
	
	// Aloca matrizes de entrada e o resultado C
	A = allocMatrix(s[0],s[1]);
	B = allocMatrix(s[1],s[2]);
	C = allocMatrix(s[0],s[2]);
	
<<<<<<< .mine
=======
	localMaxV = (int*)malloc(sizeof(int)*s[2]);
	for(i=0;i<s[2];i++)
		localMaxV[i] = 0;
	
>>>>>>> .r158
	task_rows = s[0]/P;					// Numero de linhas da matriz A enviadas para cada processador
	extra_rows = s[0]%P;				// Numero de linhas a mais que sao enviadas para o ultimo processador quando s[0] nao é divisivel por P
	
	// Calcula intervalo de iteracao de cada processador
	from = myrank*(task_rows);			
	to = from+task_rows;
	
	// Soma as linhas extras para o ultimo processador
	if(myrank==P-1)
		to+=extra_rows;
	
	// No mestre le os valores de entrada das matrizes A e B
	if(myrank==0){
		// Matriz A
		for(i=0;i<s[0];i++){
			for(j=0;j<s[1];j++){
				scanf("%d",&v);
				A[i][j] = v;
			}
		}
		
		// Matriz B
		for(i=0;i<s[1];i++){
			for(j=0;j<s[2];j++){
				scanf("%d",&v);
				B[i][j] = v;
			}
		}

		//print(A, s[0], s[1]);
		//print(B, s[1], s[2]);

		// Calcula memoria a ser enviada para cada processador
		// Valores necessarios para a funcao Scatterv
	
		displs = (int*)malloc(sizeof(int)*P);		
		sendcount = (int*)malloc(sizeof(int)*P);
		for(i=0;i<P;i++){
			displs[i] = i*task_rows*s[1]; 			// Valores da matriz A sao enviados a partir do offset indicado por displs[i]. 
			sendcount[i] = task_rows*s[1];			// Quantidade de inteiros a serem lidos a partir do offset dado.
		}
		sendcount[P-1] += extra_rows*s[1];			// Inteiros das linhas extras
	}
	
	// Envia a parte correspondente da matriz A para cada no escravo
	MPI_Scatterv(A[0], sendcount, displs, MPI_INT, A[from], (to-from)*s[1], MPI_INT, 0, MPI_COMM_WORLD);
	
	// Envia matriz B inteira para os nos escravos
	MPI_Bcast(B[0],s[1]*s[2],MPI_INT, 0, MPI_COMM_WORLD);
	
	// Cada no executa sua porcao da multiplicacao
	// por meio do auxilio da biblioteca pthreads
	runThreads();

	/*
	 * Versao da multiplicacao sem a utilizacao de threads executa esse bloco de codigo:
	 *
	for (i=from; i<to; i++){ 
		for (j=0; j<s[2]; j++) {
			C[i][j]=0;
			for (k=0; k<s[1]; k++)
				C[i][j] += A[i][k]*B[k][j];
		}
	}
	 *
	 */
<<<<<<< .mine
	 
=======
	 	
>>>>>>> .r158
	// Nó mestre calcula quantidade de dados a serem lidas dos nós escravos
	if(myrank==0){
		// Calcula memoria a ser recolhida de cada processador
		for(i=0;i<P;i++){
			displs[i] = i*(task_rows)*s[2];		// offset a partir do qual a leitura e feita.
			sendcount[i] = (task_rows)*s[2];	// inteiros a serem lidos a partir do offset dado.
		}
		sendcount[P-1] += (extra_rows)*s[2];	// valores extras do ultimo processo.
	}
	
	// Leitura de dados dos nos escravos para o no mestre
	
<<<<<<< .mine
	MPI_Gatherv(C[from], (to-from)*s[2], MPI_INT, C[0], sendcount, displs, MPI_INT, 0, MPI_COMM_WORLD);
=======
	MPI_Gatherv(C[from], (to-from)*s[2], MPI_INT, C[0], sendcount, displs, MPI_INT, 0, MPI_COMM_WORLD);	

	//MPI_Reduce( localMaxV, maxV, s[2], MPI_INT, MPI_MAX, 0, MPI_COMM_WORLD);
>>>>>>> .r158
	
	// No mestre imprime o resultado.
	if(myrank==0){
<<<<<<< .mine
=======
	//	printf("excentricidades: ");
	//	print(&maxV,1,s[2]);
	
>>>>>>> .r158
	//	print(C,s[0],s[2]);	
		free(sendcount);
		free(displs);
	}
	
	free(A);
	free(B);
	free(C);
	
	// Fim.
	MPI_Finalize();
	return 0;
}
